/*
 * THIS SOFTWARE IS PROVIDED BY MARK SALYZYN ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
 * NO EVENT SHALL MARK SALYZYN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *	@(#)Fd.m		1.1 (Salyzyn)	95/12/05
 */

#import "Fd.h"

/*
 *	A non-instantiated variable telling us which major number we are
 *	using. We do not allow more than one instance of the FD driver.
 */
static int	FdMajor ;

@implementation Fd

#define Fdclose	nulldev

+ (BOOL) probe : description
{	Fd *			dev ;
	IODirectDevice *	desc ;
	char			Name[sizeof(FdDeviceName) + 1] ;
	unsigned		instance ;
	const char *		String ;
	IOConfigTable *		config = [description configTable] ;
	const char *		cp ;
	static int		Fdopen(), Fdclose(), Fdread(), Fdwrite(),
				Fdioctl(), Fdselect() ;
	extern			nodev(), nulldev() ;
	extern char *		strcpy() ;

	/*
	 *	Find out what we have attached.
	 */
	if ((dev = [self alloc]) == nil) {
		IOLog ("%s: Failed to allocate an instance\n", FdDeviceName) ;
		return NO ;
	}

	strcpy (Name, FdDeviceName)[sizeof(Name) - 1] = '\0' ;
	instance = 0 ;
	if (String = [config valueForStringKey : "Instance"]) {
		for (cp = String; ('0' <= *cp) && (*cp <= '9'); ++cp)
			instance = (instance * 10) + *cp - '0' ;
		[config freeString : String] ;
	}
	Name[sizeof(Name) - 2] = instance + '0' ;
	[dev setName : Name] ;
	[dev setDeviceKind : FdDeviceKind] ;
	[dev setUnit : instance] ;
	if (instance >= 1) {
		[dev free] ;
		IOLog ("%s Too many instances\n", [self name]) ;
		return NO ;
	}
	if ((FdMajor = IOAddToCdevsw (
	    Fdopen,	 /* IOSwitchFunc openFunc	*/
	    Fdclose,	 /* IOSwitchFunc closeFunc	*/
	    Fdread,	 /* IOSwitchFunc readFunc	*/
	    Fdwrite,	 /* IOSwitchFunc writeFunc	*/
	    Fdioctl,	 /* IOSwitchFunc ioctlFunc	*/
	    nodev,	 /* IOSwitchFunc stopFunc	*/
	    nulldev,	 /* IOSwitchFunc resetFunc	*/
	    Fdselect,	 /* IOSwitchFunc selectFunc	*/
	    nodev,	 /* IOSwitchFunc mmapFunc	*/
	    nodev,	 /* IOSwitchFunc getcFunc	*/
	    nodev)	 /* IOSwitchFunc putcFunc	*/) == -1) {
		[dev free] ;
		IOLog ("%s failed to place into Cdevsw\n", [self name]) ;
		return NO ;
	}
	[dev registerDevice] ;

	if ((desc = [dev initFromDeviceDescription : description]) == nil) {
		IOLog ("%s: Failed to init Device Description\n",FdDeviceName) ;
		[dev free] ;
		return NO ;
	}

	return YES ;
}

/*
 *	Name: getIntValues : values forParameter : parameter count : count
 *	Handle providing information back to user routines. This is an
 *	out-of-bound Kernel Server channel. We allow asking for the following:
 *		IOMajorDevice	Major device of this instance of the driver
 *		IONumberDevice	Number of ports handled on this instance.
 */
- (IOReturn) getIntValues : (unsigned *)values
	     forParameter : (IOParameterName) parameter
		    count : (unsigned *) count
{	unsigned	maxCount = *count ;

	if (maxCount == 0)
		maxCount = IO_MAX_PARAMETER_ARRAY_LENGTH ;

	if (strcmp(parameter, "IOMajorDevice") == 0) {
		values[0] = FdMajor ;
		*count = 1 ;
		return IO_R_SUCCESS ;
	}

	if (strcmp (parameter, "IONumberDevice") == 0) {
		values[0] = NOFILE ;
		*count = 1 ;
		return IO_R_SUCCESS ;
	}

	return [super getIntValues : values
		      forParameter : parameter
			     count : &maxCount] ;
}

/* ARGSUSED */
static int
Fdopen (dev_t dev, int flag, int mode)
{	struct file *	fp ;
	struct file *	dfp ;

	/*
	 *	Find the file pointers for the file descriptors, both
	 *	must be valid.
	 */
	if (((fp = getf (u.u_r.r_val1)) == NULL)
	 || ((dfp = getf (minor (dev))) == NULL)
	 || (fp == dfp))
		return (EBADF) ;
	/*
	 *	Make sure opened up in same mode
	 */
	if (((flag & (FREAD | FWRITE)) | dfp->f_flag) != dfp->f_flag)
		return (EACCES) ;

	return (0) ;
}

static int
Fdread (dev_t dev, struct uio *uio, int flag)
{
	if (u.u_ap[0] == minor (dev))
		return (EBADF) ;
	u.u_ap[0] = minor (dev) ;
	/*
	 *	Ok, so this is MACH specific, saves us some stack
	 *	space though!
	 */
	rwuio (uio, UIO_READ) ;
	return (u.u_error) ;
}

static int
Fdwrite (dev_t dev, struct uio *uio, int flag)
{
	if (u.u_ap[0] == minor (dev))
		return (EBADF) ;
	u.u_ap[0] = minor (dev) ;
	rwuio (uio, UIO_WRITE) ;
	return (u.u_error) ;
}

static int
Fdioctl (dev_t dev, int cmd, caddr_t data, int flag)
{
	if (u.u_ap[0] == minor (dev))
		return (EBADF) ;
	u.u_ap[0] = minor (dev) ;
	ioctl () ;
	return (u.u_error) ;
}

static int
Fdselect (dev_t dev, int rw)
{
	struct file *	fp ;

	if (((fp = getf (minor(dev))) == NULL)
	 || (fp->f_ops == NULL)
	 || (fp->f_ops->fo_select == NULL))
		return (1) ;
	return ((*fp->f_ops->fo_select)(fp, rw)) ;
}

@end
